
/*
 * Copyright 2008 Friendster Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
 
 * Unless required by applicable law or agreed to in writing, 
 * software distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Configuration;
using System.Security.Cryptography;
using System.Globalization;
using System.IO;
using System.Web;
using System.Reflection;

namespace Friendster.Service.Core
{
    /// <summary>
    /// This class formats request to send to friendster.
    /// Also gets the response and pass to Friendster class.
    /// </summary>
    public sealed class FriendsterRequest
    {
        //Fields
        private static string _session;
        private string _apiURL = "http://api.friendster.com";
        private string _version = "v1";
        private static string _authToken;
        private static string _userID;
        private string _format = "XML";
        private string _nonce;
        private string _language = "en-US";
        private FriendsterRequestParameters _parameters;
        
        //Properties 
        public string Session
        {
            get
            {
                return _session;
            }
        }
        public string APIKey
        {
            get
            {
                return ConfigurationManager.AppSettings["APIKey"].ToString();
            }
        }
        public string Secret
        {
            get
            {
                return ConfigurationManager.AppSettings["Secret"].ToString();
            }
        }
        public string UserID
        {
            get
            {
                return _userID;
            }
        }
        public FriendsterRequestParameters Parameters
        {
            get
            {
                return this._parameters;
            }
        }

        //Methods
        /// <summary>
        /// Constructor. Creates the session if not created yet.
        /// </summary>
        /// <param name="session"></param>
        public FriendsterRequest(string session)
        {
            if (string.IsNullOrEmpty(session) || session.Contains("widget_login"))
                _session = this.CreateSession(_authToken);
            else
                _session = session;
            this._parameters = new FriendsterRequestParameters();
        }
        
        /// <summary>
        /// Creates the session. Also gets the authentication token if not has already one.
        /// </summary>
        /// <param name="authToken"></param>
        /// <returns>Session string</returns>
        public string CreateSession(string authToken)
        {
            _userID = null;
            if (string.IsNullOrEmpty(authToken))
            {
                authToken = CreateAuthToken();
                _authToken = authToken;
            }
            string str = null;
            try
            {
                HttpWebResponse response = (HttpWebResponse)CreateRequest("POST", "session").GetResponse();
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    FriendsterResponse response2 = new FriendsterResponse(response.GetResponseStream());
                    if (!response2.IsError)
                    {
                        ArrayList myResult = (ArrayList)response2.Result;
                        Hashtable result = (Hashtable)myResult[0];
                        if (result != null)
                        {
                            str = (string)result["session_key"];
                            _userID = (string)result["uid"];
                        }
                    }
                }
            }
            catch
            {
                string widgetLogin = "http://www.friendster.com/widget_login.php?api_key=";
                widgetLogin += this.APIKey;
                widgetLogin += "&auth_token=";
                widgetLogin += _authToken;
                System.Web.HttpContext.Current.Response.Redirect(widgetLogin);
            }
            return str;
        }
        /// <summary>
        /// Gets authentication token.
        /// </summary>
        /// <returns>Token String</returns>
        public string CreateAuthToken()
        {
            Hashtable result = null;
            try
            {
                HttpWebResponse response = (HttpWebResponse)CreateRequest("POST", "token").GetResponse();

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    FriendsterResponse response2 = new FriendsterResponse(response.GetResponseStream());
                    if (!response2.IsError)
                    {
                        ArrayList myResult = (ArrayList)response2.Result;
                        result = (Hashtable)myResult[0];
                    }
                }
            }
            catch (Exception e)
            {
                string methodName = "token";
                string code = "";
                
                if (!string.IsNullOrEmpty(e.Message))
                    code = e.Message.Substring(e.Message.IndexOf("(") + 1, 3);

                FriendsterException exp = new FriendsterException(methodName, code);
                throw new InvalidOperationException(exp.ErrorMessage);
            }
            return (string)result["auth_token"];
        }

        /// <summary>
        /// Invokes specified method. Send request according to the method.
        /// Gets the method response.
        /// </summary>
        /// <param name="method"></param>
        /// <param name="urlSuffix"></param>
        /// <returns>Friendster Response</returns>
        public FriendsterResponse InvokeMethod(string method, string urlSuffix)
        {
            HttpWebRequest request = this.CreateRequest(method, urlSuffix);
            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch(Exception e)
            {
                string methodName = "";
                string code = "";
                int a = urlSuffix.IndexOf("/");
                if (a > 0)
                    methodName = urlSuffix.Substring(0, urlSuffix.IndexOf("/"));
                else
                    methodName = urlSuffix;
                if (!string.IsNullOrEmpty(e.Message))
                    code = e.Message.Substring(e.Message.IndexOf("(")+1, 3);
                
                FriendsterException exp = new FriendsterException(methodName,code);
                throw new InvalidOperationException(exp.ErrorMessage);
            }
            return new FriendsterResponse(response.GetResponseStream());
        }

        /// <summary>
        /// Uploads photo for specified UID
        /// </summary>
        /// <param name="uid"></param>
        /// <param name="filename"></param>
        /// <param name="photoStream"></param>
        /// <param name="contentType"></param>
        /// <param name="caption"></param>
        /// <param name="albumID"></param>
        public FriendsterResponse UploadFile(string uid, string filename, Stream photoStream, string contentType, string caption, string albumID)
        {
            string pathInfo = "/" + this._version + "/photos";//" + _userID;
            SortedList paramList = new SortedList();
            paramList.Add("api_key", this.APIKey);
            //paramList.Add("format",this._format);
            //paramList.Add("lang", this._language);
            paramList.Add("session_key", _session);
            if(!string.IsNullOrEmpty(uid))
                paramList.Add("uid",uid);
            paramList.Add("aid", albumID);
            paramList.Add("caption", caption);

            // Get unique call_id and include this to sorted param list
            this._nonce = utimestamp().ToString();
            paramList.Add("nonce", this._nonce);
            // Concate all the params in key=value format.
            string preSig = "";
            for (int i = 0; i < paramList.Count; i++)
            {
                preSig += paramList.GetKey(i).ToString() + "=" + paramList.GetByIndex(i).ToString();
            }
            string sig = CreateSignature(pathInfo + preSig + this.Secret);
            // Add the sig to parameter list
            paramList.Add("sig", sig);

            // Generate requestURL
            string requestURL = "";
            requestURL = this._apiURL + "/" + this._version + "/photos";

            // Create a webRequest with generated requestURL.
            // Finally return the created request to calling method
            string str = new Guid().ToString("D");
            str = "frontier";
            Uri u = new Uri(requestURL);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(u);
            request.ContentType = "multipart/form-data; boundary=" + str;
            request.Timeout = 1000000000;
            request.ReadWriteTimeout = 1000000000;
            request.KeepAlive = false;
            request.ProtocolVersion = HttpVersion.Version10;
            request.Method = "POST";

            StringBuilder builder = new StringBuilder(0x400);
            for (int i = 0; i < paramList.Count; i++)
            {
                builder.Append("--");
                builder.Append(str);
                builder.Append("\r\n");
                builder.Append("Content-Disposition: form-data; name=\"");
                builder.Append(paramList.GetKey(i));
                builder.Append("\"\r\n\r\n");
                builder.Append(paramList.GetByIndex(i));
                builder.Append("\r\n");
            }
            builder.Append("--");
            builder.Append(str);
            builder.Append("\r\n");
            builder.Append("Content-Disposition: form-data; filename=\"");
            builder.Append(filename);
            builder.Append("\"\r\n");
            builder.Append("Content-Type: ");
            builder.Append(contentType);
            builder.Append("\r\n\r\n");

            string s = builder.ToString();
            string str3 = "\r\n--" + str;
            byte[] dataBytes = Encoding.UTF8.GetBytes(s);
            byte[] sepBytes = Encoding.UTF8.GetBytes(str3);
            byte[] photoBytes = new byte[photoStream.Length];
            int count = 0;
            count = photoStream.Read(photoBytes, 0, (int)photoStream.Length); // Convart photo stream to byte array
            // Concat dataBytes and photoBytes array
            byte[] dataPhotoBytes = new byte[dataBytes.Length + photoBytes.Length]; // declaring a byte array as the size of the sum of dataBytes array size and photoBytes array size
            dataBytes.CopyTo(dataPhotoBytes, 0); // copying dataBytes array into dataPhotoBytes array from 0 offset
            photoBytes.CopyTo(dataPhotoBytes, dataBytes.Length); // copying photoBytes array into dataPhotoBytes array after the offset of the previous copying 
            // End concat

            // Concat dataPhotoBytes and sepBytes array
            byte[] dataPhotoSepBytes = new byte[dataPhotoBytes.Length + sepBytes.Length]; // declaring a byte array as the size of the sum of dataPhotoBytes array size and sepBytes array size
            dataPhotoBytes.CopyTo(dataPhotoSepBytes, 0); // copying dataPhotoBytes array into dataPhotoSepBytes array from 0 offset
            sepBytes.CopyTo(dataPhotoSepBytes, dataPhotoBytes.Length); // copying sepBytes array into dataPhotoSepBytes array after the offset of the previous copying 
            // End concat

            request.ContentLength = dataPhotoSepBytes.Length;
            try
            {
                using (Stream stream = request.GetRequestStream())
                {
                    stream.Write(dataPhotoSepBytes, 0, dataPhotoSepBytes.Length);
                }
            }
            catch (Exception e)
            {
                e.Message.ToString();
            }
          
            // HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (Exception e)
            {
                string methodName = "photos";
                string code = "";
                if (!string.IsNullOrEmpty(e.Message))
                    code = e.Message.Substring(e.Message.IndexOf("(") + 1, 3);

                FriendsterException exp = new FriendsterException(methodName, code);
                throw new InvalidOperationException(exp.ErrorMessage);
            }

            return new FriendsterResponse(response.GetResponseStream());
        }
      
        /// <summary>
        /// Creates request stream according to friendster rules
        /// </summary>
        /// <param name="method"></param>
        /// <param name="urlSuffix"></param>
        /// <returns>Request stream</returns>
        public HttpWebRequest CreateRequest(string method, string urlSuffix)
        {
            // path info for the request
            string pathInfo = "/" + this._version + "/" + urlSuffix;

            // Create sorted key-value pair of request parameters
            SortedList paramList = new SortedList();
            paramList.Add("api_key", this.APIKey);
            paramList.Add("format",this._format);
            paramList.Add("lang", this._language);
            paramList.Add("session_key",_session);
            // if the request is for token, exclude session_key from the parameter list
            if (string.CompareOrdinal(urlSuffix,"token")==0)
            {
                paramList.Remove("session_key");
            }
            // If the request is for session key, include token and exclude session_key to and from pram list
            else if (string.CompareOrdinal(urlSuffix, "session")==0)
            {
                paramList.Remove("session_key");
                paramList.Add("auth_token",_authToken);
            }
            if (this._parameters != null)
            {
                foreach (KeyValuePair<string, string> kvp in this._parameters)
                {
                    paramList.Add(kvp.Key, kvp.Value);
                }
            }
            // Get unique call_id and include this to sorted param list
            this._nonce = utimestamp().ToString();
            paramList.Add("nonce",this._nonce);
            // Concate all the params in key=value format.
            string preSig = "";
            for (int i = 0; i < paramList.Count; i++)
            {
                preSig += paramList.GetKey(i).ToString() + "=" + paramList.GetByIndex(i).ToString();
            }
            
            // Create signature: concate(pathinfo+presig+secret_key)
            string sig = CreateSignature(pathInfo + preSig + this.Secret);
            
            // Add the sig to parameter list
            paramList.Add("sig",sig);
            
            // Generate requestURL
            string requestURL="";
            
            for (int i = 0; i < paramList.Count; i++)
            {
                requestURL += paramList.GetKey(i).ToString() + "=" + paramList.GetByIndex(i).ToString() + "&";
            }
            requestURL = this._apiURL + pathInfo + "?" + requestURL.Substring(0, requestURL.Length - 1);
            
            // Create a webRequest with generated requestURL.
            // Finally return the created request to calling method
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestURL);
            
            request.Method = method;
            request.Referer = "http://localhost:1517/API/";
            return request;
        }

        /// <summary>
        /// Creates Signature.
        /// </summary>
        /// <param name="sigParam"></param>
        /// <returns></returns>
        private string CreateSignature(string sigParam)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(sigParam);
            byte[] buffer = MD5.Create().ComputeHash(Encoding.Default.GetBytes(builder.ToString()));
            builder = new StringBuilder();
            for (int i = 0; i < buffer.Length; i++)
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", new object[] { buffer[i] });
            }
            return builder.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>Unix Timestamp</returns>
        private static double utimestamp()
        {
            System.DateTime UnixBase = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
            System.DateTime dtm = System.DateTime.Now;
            return (double)dtm.Subtract(UnixBase).TotalSeconds;
        }
    }
}
